package com.metal.meta.service;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.metal.meta.util.ThreadLocalUtil;
import com.metal.meta.exception.BusinessException;
import com.metal.meta.exception.ResponseCode;
import com.metal.meta.util.MongoDBUtil;
import com.mongodb.client.FindIterable;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.junit.Test;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @ClassName FormService
 * @Description
 * @Author liukun
 * @Date 2022/9/7 17:29
 */
@Slf4j
public class FormService {

    /**
     * 加载表单配置数据
     * @return
     */
    public static List<JSONObject> loadFormConfig(JSONObject formQueryJson) {

        JSONObject queryFormJson = new JSONObject();
        queryFormJson.putAll(formQueryJson);
        queryFormJson.put("del_flag", 0);
        FindIterable<Document> documents = MongoDBUtil.getDocument("configure").find(Document.parse(queryFormJson.toJSONString()));
        List<JSONObject> jsonObjects = MongoDBUtil.resultList(documents);

        return jsonObjects;
    }

    /**
     * 子域层级填充，包含children的排序
     * @param formQueryJson
     * @return
     */
    public static JSONObject levelFill(JSONObject formQueryJson) {

        List<JSONObject> formConfigList = loadFormConfig(formQueryJson);
        Map<String, JSONObject> nodeMap = new HashMap<>();
        // 构造快速查询的map，key为column_name
        formConfigList.stream().forEach(e -> {
            nodeMap.put(e.getString("column_name"), e);
        });

        // 获取头部根节点
        JSONObject rootNode = getFormRoot(formConfigList);
        rootNode.remove("_id");
        for (JSONObject currentNode: formConfigList) {
            currentNode.remove("_id");
            if (currentNode != rootNode) {
                // 非头部描述节点，就可以处理其层级关系
                JSONObject parent = nodeMap.get(currentNode.getString("parent_column_name"));
                if (parent == null) {
                    // 此节点无父节点，无需继续执行。
                    continue;
                }
                if (parent.containsKey("children")) {
                    // 固定给返参加上children字段，用来存储parent的下级数据
                    JSONArray children = parent.getJSONArray("children");
                    children.add(currentNode);
                    children.sort(Comparator.comparing(item ->((JSONObject)item).getString("sort")));
                }
                else {
                    JSONArray children = new JSONArray();
                    children.add(currentNode);
                    parent.put("children", children);
                }
            }
        }

        return rootNode;
    }

    public static void addFormConfig(JSONObject requestJsonObj, JSONArray addJsonArray) {
        // 在插入数据前，需要判断 namespace、system、system_module、form、parent过滤出来的列表里，是否已经此column_name
        // 这个考虑在MongoDB中建唯一索引。

        for (Object node: addJsonArray) {
            ((LinkedHashMap)node).put("namespace", requestJsonObj.get("namespace"));
            ((LinkedHashMap)node).put("system", requestJsonObj.get("system"));
            ((LinkedHashMap)node).put("system_module", requestJsonObj.get("system_module"));
            ((LinkedHashMap)node).put("form", requestJsonObj.get("form"));
        }
        addFormConfig(addJsonArray);
    }

    public static void addFormConfig(JSONArray addJsonArray) {
        // 不允许同一个form中有相同的column_name；这一块逻辑还有缺陷，比如同样的"贷款余额"，在房产信息域和负债信息域都可能存在。
        /*for (Object node: addJsonArray) {
            JSONObject nodeJsonObj = (JSONObject) JSON.toJSON(node);
            JSONObject queryColumnName = new JSONObject();
            queryColumnName.put("form", formName);
            queryColumnName.put("column_name", nodeJsonObj.getString("column_name"));
            FindIterable<Document> documents = MongoDBUtil.getDocument("zd", "configure").find(Document.parse(queryColumnName.toJSONString()));
            if (!CollectionUtils.isEmpty(MongoDBUtil.resultList(documents))) {
                throw new BusinessException(ResponseCode.DUPLICAT_COLUMN_IN_FORM, nodeJsonObj.toJSONString());
            }
        }*/

        // 插入执行
        for (Object node: addJsonArray) {
            JSONObject insert = new JSONObject();
            insert.putAll((JSONObject) JSON.toJSON(node));
            // 参考：https://www.dianjilingqu.com/353098.html
            insert.put("uuid", UUID.randomUUID().toString());
            insert.put("add_time", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            insert.put("update_time", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            insert.put("del_flag", 0);
            // 将正在处理的数据加入到ThreadLocal，当出现异常时，将异常数据反馈给调用方
            ThreadLocalUtil.set("adding", insert);
            MongoDBUtil.getDocument("configure").insertOne(Document.parse(insert.toJSONString()));
            log.info("新增成功{}", insert.toJSONString());
        }
    }

    public static void updateFormConfig(JSONArray updateJsonArray) {
        for (Object node: updateJsonArray) {
            JSONObject nodeJsonObj = (JSONObject) JSON.toJSON(node);
            nodeJsonObj.put("update_time", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            JSONObject query = new JSONObject();
            query.put("uuid", nodeJsonObj.getString("uuid"));
            Document doc2 = new Document("$set", Document.parse(nodeJsonObj.toJSONString()));
            // 将正在处理的数据加入到ThreadLocal，当出现异常时，将异常数据反馈给调用方
            ThreadLocalUtil.set("updating", doc2);
            UpdateResult updateResult = MongoDBUtil.getDocument("configure").updateOne(Document.parse(query.toJSONString())
                    , doc2
                    , new UpdateOptions().upsert(false)
            );
            log.info("更新结果{}", updateResult);
        }
    }

    public static void deleteFormConfig(JSONArray deleteJsonArray) {
        for (Object node: deleteJsonArray) {
            JSONObject nodeJsonObj = (JSONObject) JSON.toJSON(node);
            JSONObject query = new JSONObject();
            query.put("uuid", nodeJsonObj.getString("uuid"));
            JSONObject update = new JSONObject();
            update.put("del_flag", 1);
            update.put("update_time", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            Document doc2 = new Document("$set", Document.parse(update.toJSONString()));
            // 将正在处理的数据加入到ThreadLocal，当出现异常时，将异常数据反馈给调用方
            ThreadLocalUtil.set("deleting", doc2);
            UpdateResult updateResult = MongoDBUtil.getDocument("configure").updateOne(Document.parse(query.toJSONString())
                    , doc2
                    , new UpdateOptions().upsert(false)
            );
            log.info("删除结果{}", updateResult);
        }
    }

    /**
     * 处理excel数据，逐行处理，将其添加到对应的 "添加"、"修改"、"删除" List中
     * @param configList
     * @return
     */
    public static Boolean handleExcelLines(List<LinkedHashMap> configList) {
        JSONArray addJsonArray = new JSONArray();
        JSONArray updateJsonArray = new JSONArray();
        JSONArray deleteJsonArray = new JSONArray();
        for (LinkedHashMap map: configList) {
            // 转JsonObject，其中会涉及到比如choose是列表类型的转换。
            JSONObject jsonObject = parseAsJsonObject(map);

            // 如果没有uuid这个字段，就是add
            AtomicBoolean uuidValueIsEmpty = new AtomicBoolean(false);
            if (StringUtils.isEmpty(jsonObject.getString("uuid"))) {
                uuidValueIsEmpty.set(true);
            }
            // 查看是否是删除
            int del_flag = jsonObject.getIntValue("del_flag");

            // 将列表进行归类，放入 addJsonArray、deleteJsonArray、updateJsonArray
            if (uuidValueIsEmpty.get()) {
                // uuid的值为空，要执行add
                addJsonArray.add(jsonObject);
            }
            else {
                // uuid的值不为空，要执行delete或update操作
                if (del_flag == 1) {
                    // 有uuid，并且del_flag==1，就是delete
                    deleteJsonArray.add(jsonObject);
                }
                if (del_flag == 0) {
                    // 有uuid，del_flag==0，就是update
                    updateJsonArray.add(jsonObject);
                }
            }
        }
        log.info("【excel数据归类完成】");

        // 对不同的分类数据，进行mongo配置更新
        if (addJsonArray.size() > 0) {
//            String form = ((JSONObject)addJsonArray.get(0)).getString("form");
            addFormConfig(addJsonArray);
            log.info("excel数据新增完成");
        }
        if (updateJsonArray.size() > 0) {
            updateFormConfig(updateJsonArray);
            log.info("excel数据新增完成");
        }
        if (deleteJsonArray.size() > 0) {
            deleteFormConfig(deleteJsonArray);
            log.info("excel数据删除完成");
        }
        log.info("【excel数据操作全部完成】");

        return true;
    }

    private static JSONObject parseAsJsonObject(LinkedHashMap map) {
        JSONObject originJsonObject = (JSONObject) JSONObject.parse(JSONObject.toJSONString(map));
        JSONObject newJsonObject = new JSONObject();
        originJsonObject.keySet().forEach(key -> {
            String newKey = key.substring(0, key.indexOf("\n")==-1 ? key.length(): key.indexOf("\n"));
            if (!"choose,children_unique".contains(newKey)) {
                newJsonObject.put(newKey, originJsonObject.get(key));
            }
            else {
                newJsonObject.put(newKey, JSONArray.parseArray(originJsonObject.getString(key)));
            }
        });
        return newJsonObject;
    }

    /**
     * 通过mongodb表单配置信息，生成mysql建表语句
     * @param queryJsonObject
     * @param configJsonObjList
     * @return
     */
    public static String makeTableSqlByConfig(JSONObject queryJsonObject, List<JSONObject> configJsonObjList) {
        String sql = "CREATE TABLE if not exists `%s` (" +
                " %s " +
                "  PRIMARY KEY (`uuid`) USING BTREE" +
                ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='%s';";
        String tableName = queryJsonObject.getString("form");
        String allColumnsString = "";
        String formComment = "";
        for (JSONObject jsonObject: configJsonObjList) {
            String columnName = jsonObject.getString("column_name");
            String type = jsonObject.getString("type");
            if (type.equalsIgnoreCase("form")) {
                formComment = jsonObject.getString("column_desc");
                continue;
            }
            String typeSql = "";
            if (type.equalsIgnoreCase("text") || type.equalsIgnoreCase("select") || type.equalsIgnoreCase("table")) {
                if (columnName.equalsIgnoreCase("uuid")) {
                    typeSql = " varchar(128) ";
                }
                else {
                    typeSql = " varchar(512) ";
                }
            }
            if (type.equalsIgnoreCase("int")) {
                typeSql = " int(4) ";
            }
            if (type.equalsIgnoreCase("datetime")) {
                typeSql = " datetime ";
            }
            if (!StringUtils.hasLength(typeSql)) {
                throw new BusinessException(ResponseCode.NOT_SUPPORT_TYPE, type);
            }
            String comment = jsonObject.getString("column_desc");
            String columnString = String.format("`%s` %s COMMENT '%s',", columnName, typeSql, comment);
            allColumnsString += columnString;
        }
        String createTableSql = String.format(sql, tableName, allColumnsString, formComment);
        return createTableSql;
    }

    @Test
    public void testFill() {
        JSONObject formQueryJson = new JSONObject();
        formQueryJson.put("form", "customer");
        JSONObject customer = levelFill(formQueryJson);
        log.info("表单信息{}", customer.toJSONString());
    }

    /**
     * 从 formConfigList 中找到：type=form的，这个是用来标记此表格的头部，所有的表格配置，都将挂在此元素下面。
     * @return
     */
    private static JSONObject getFormRoot(List<JSONObject> formConfigList) {
        return formConfigList.stream().filter(item -> {
            if (item.getString("type").equals("form")) {
                return true;
            }
            return false;
        }).collect(Collectors.toList()).get(0);
    }

}
